package logic.common.player;

import java.util.ArrayList;
import java.util.List;

import fileHandling.language.properties.PlayerStats;
import gameStates.GameTimeListener;
import gameStates.absGamesStates.AbsIngameState;
import gameStates.network.IDObject;


import networking.messages.playerMessages.PlayerAddMessage;
import networking.messages.playerMessages.PlayerUpdateMessage;
import networking.messages.syncMessages.ValueType;

import logic.StatsContainer;
import logic.common.Squad;
import logic.common.missions.Mission;
import logic.common.player.awards.Award;
import logic.common.player.awards.AwardManager;
import logic.common.player.awards.PlayerAwardListener;
import logic.common.player.awards.PlayerGotAwardEvent;
import logic.common.player.playerListeners.HunterChangedEvent;
import logic.common.player.playerListeners.KillAssistEvent;
import logic.common.player.playerListeners.KillEvent;
import logic.common.player.playerListeners.MissionActivatedEvent;
import logic.common.player.playerListeners.MissionAssistEvent;
import logic.common.player.playerListeners.NearStationChangedEvent;
import logic.common.player.playerListeners.PlayerListener;
import logic.common.player.playerListeners.PlayerMissionListener;
import logic.common.player.playerListeners.PlayerSpawnLocation;
import logic.common.player.playerListeners.PlayerSquadListener;
import logic.common.player.playerListeners.PlayerStationListener;
import logic.common.player.playerListeners.PlayerStatusManager;
import logic.common.player.playerListeners.PlayerTeamListener;
import logic.common.player.playerListeners.PlayerValueListener;
import logic.common.player.playerListeners.RespawnEvent;
import logic.common.player.playerListeners.SelectedSpawnChangedEvent;
import logic.common.player.playerListeners.TargetChangedEvent;
import logic.common.team.Team;
import logic.common.team.teamListeners.MissionAddedEvent;
import logic.common.team.teamListeners.MissionCompletedEvent;
import logic.common.team.teamListeners.MissionDeletedEvent;
import logic.common.team.teamListeners.TeamMissionListener;
import logic.nodes.TeamNode;
import logic.ships.hunter.Hunter;
import logic.weapons.Weapon;
import main.InitGame;
import main.listeners.ListenerContainer;
import main.listeners.ListenerKeeper;
import map.spawnStation.SpawnCaptureController;
import map.spawnStation.SpawnPosition;
import map.spawnStation.SpawnStation;

import ai.AILevel;
import ai.PilotController;

import com.jme.math.Vector3f;

public class Player implements ListenerKeeper, IDObject {
	
	public static final int KILL_ASSIST_MONEY = 20;
		
	private String name;
	private Team team, enemyTeam;
	private int id, spawnTime;
	
	private Hunter hunter, currentTarget;
	private TeamNode botTarget;
	
	private SpawnPosition currentSpawnPosition;
	private boolean isAlive, isHuman, spawnable;
		
	private ListenerContainer squadListeners;
	private Squad squad;
	private PlayerSpawnLocation playerSpawnLocation;
	
	private PilotController pilotController;
	private ListenerContainer playerListeners;
	private ListenerContainer playerTeamListeners;
	
	private PlayerStatusManager playerValueManager;
	
	private ListenerContainer missionListeners;
	private Mission activeMission;
		
	private SpawnPosition nearSpawnStation;
	private SpawnCaptureController caputureController;
	
	private ListenerContainer playerStationListeners;
	
	private StatsContainer stats;
	
	private TeamMissionListener missionListener;
	private GameTimeListener gameTimeListener;
	
	private List<Award> awards;
	private ListenerContainer playerAwardListeners;
	
	public Player(PlayerAddMessage pam, Team team, Team enemyTeam) {
		this(pam.getName(), pam.isHuman(), team, enemyTeam);
		
		stats.updateStat(PlayerStats.Kills, pam.getKills());
		stats.updateStat(PlayerStats.Deaths, pam.getDeaths());
		
		isAlive = pam.isAlive();
	}
	
	public Player(String name, boolean isHuman, final Team team, Team enemyTeam) {
		this.name = name;
		this.isHuman = isHuman;
		this.enemyTeam = enemyTeam;
		
		stats = new StatsContainer(PlayerStats.values());
		
		AbsIngameState ingameState = team.getIngameState();
		spawnTime = ingameState.getGame().getRespawnTime();
		
		playerValueManager = new PlayerStatusManager();
		
		caputureController = new SpawnCaptureController(this);
		
		playerListeners = new ListenerContainer(PlayerListener.class);
		
		playerTeamListeners = new ListenerContainer(PlayerTeamListener.class);
		
		playerStationListeners = new ListenerContainer(PlayerStationListener.class);
		
		missionListeners = new ListenerContainer(PlayerMissionListener.class);
		
		squadListeners = new ListenerContainer(PlayerSquadListener.class);
		
		playerValueManager.changeValue(ValueType.Money, team.getPilotMoney());
		playerValueManager.changeValue(ValueType.Kills, 0);
		playerValueManager.changeValue(ValueType.Deaths, 0);
		playerValueManager.changeValue(ValueType.Ping, 0);
		
		awards = new ArrayList<Award>();
		playerAwardListeners = new ListenerContainer(PlayerAwardListener.class);
		
		switchTeam(team);
		
		if(InitGame.get().isServer()) addGameTimeListener(ingameState);
		
		if(InitGame.get().isServer()) InitGame.get().getServerState().registerPlayer(this);
		
		if(ingameState.getHUDState() != null) 
			addPlayerListener(ingameState.getHUDState().getKillListener(this));
		
		missionListener = new TeamMissionListener() {
			@Override
			public void missionDeleted(MissionDeletedEvent event) {
				if(event.getMission() == activeMission && !team.getMissions().isEmpty()) 
					setActiveMission(team.getMissions().get(0));
			}
			
			@Override
			public void missionCompleted(MissionCompletedEvent event) {
				if(event.getMission() == activeMission && !team.getMissions().isEmpty()) 
					setActiveMission(team.getMissions().get(0));
				
				if(event.getPlayer() == Player.this) stats.increaseStat(PlayerStats.Completed_Missions);
			}
			
			@Override
			public void missionAdded(MissionAddedEvent event) {
				if(activeMission == null) setActiveMission(event.getMission());
			}
		};
		team.addMissionListener(missionListener);
		
		playerSpawnLocation = new PlayerSpawnLocation(this);
		
		if(InitGame.get().isServer()) {
			team.getGame().getGameSetup().registerPlayer(this);
			new AwardManager(this);
		}
	}
	
	public void addGameTimeListener(AbsIngameState ingameState) {
		gameTimeListener = new GameTimeListener() {
			@Override
			public void respawnTimeUpdate() {
				AbsIngameState ingameState = team.getIngameState();
				if(spawnTime == 0) {
					if(spawnable || isSpawnable()) {
						if(Player.this == ingameState.getPlayer()) 
							ingameState.spawnCurrentPlayer();
						else if(!isHuman() && InitGame.get().isServer()) 
							respawn();
					}
				} else spawnTime--;
				assert(spawnTime >= 0);
			}
			
			@Override
			public void gameTimeUpdate(Integer newGameTime) {}
			@Override
			public void endTimeUpdate(Integer newEndTime) {}
		};
		ingameState.addGameTimeListener(gameTimeListener);
	}
	
	public void setSpawnable(boolean spawnable) {
		assert(isHuman());
		if(spawnable && isAlive()) return;
		
		this.spawnable = spawnable;
	}
	
	public void addSquadListener(PlayerSquadListener l) { squadListeners.addListener(l); }
	
	public void removeSquadListeners(PlayerSquadListener l) { squadListeners.removeListener(l); }
	
	public void addPlayerListener(PlayerListener l) { playerListeners.addListener(l); }
	
	public void removePlayerListener(PlayerListener l) { playerListeners.removeListener(l); }
	
	public void addPlayerValueListener(PlayerValueListener l) { playerValueManager.addListener(l); }
	
	public void removePlayerValueListener(PlayerValueListener l) { playerValueManager.removeListener(l); }
	
	public void addPlayerTeamListener(PlayerTeamListener l) { playerTeamListeners.addListener(l); }
	
	public void removePlayerTeamListener(PlayerTeamListener l) { playerTeamListeners.removeListener(l); }
	
	public void addPlayerAwardListener(PlayerAwardListener l) { playerAwardListeners.addListener(l); }
	
	public void removePlayerAwardListener(PlayerAwardListener l) { playerAwardListeners.removeListener(l); }
	
	public void addAward(Award award) {
		awards.add(award);
		
		if(InitGame.get().isServer()) addMoney(award.getRewardMoney());
		
		PlayerGotAwardEvent event = new PlayerGotAwardEvent(this, award);
		playerAwardListeners.callMethod(PlayerAwardListener.PLAYERGOTAWARD, event);
	}
	
	public List<Award> getAwards() { return awards; }
	
	public void joinSquad(Squad newSquad) {
		assert(squad == null);
		squad = newSquad;
		squad.addMember(this);
		
		squadListeners.callMethod(PlayerSquadListener.JOINEDSQUAD, squad);
	}
	
	public Squad getSquad() { return squad; }
	
	public void leaveSquad() {
		squad.removeMember(this);
		
		squadListeners.callMethod(PlayerSquadListener.LEFTSQUAD, squad);
		
		squad = null;
	}
		
	public void setActiveMission(Mission mission) {
		assert(mission != null);
		
		if(activeMission != null && activeMission != mission) 
			activeMission.missionActivated(false, this);
		
		activeMission = mission;
		activeMission.missionActivated(true, this);
		
		MissionActivatedEvent event = new MissionActivatedEvent(activeMission, this);
		missionListeners.callMethod(PlayerMissionListener.MISSIONACITVATED, event);
	}
	
	public Mission getActiveMission() { return activeMission; }
	
	public void addMissionListener(PlayerMissionListener l) { missionListeners.addListener(l); }
	
	public void removeMissionListener(PlayerMissionListener l) { missionListeners.removeListener(l); }
	
	public void assist(Mission mission) {
		addMoney(mission.getAssistMoney());
		
		playerListeners.callMethod(PlayerListener.MISSIONASSIST, new MissionAssistEvent(this, mission));
	}
	
	public void assist(Player victim) {
		addMoney(KILL_ASSIST_MONEY);
		
		playerListeners.callMethod(PlayerListener.KILLASSIST, new KillAssistEvent(this, victim));
	}
	
	public void addPlayerStationListener(PlayerStationListener l) { playerStationListeners.addListener(l); }
	
	public void removePlayerStationListener(PlayerStationListener l) { playerStationListeners.removeListener(l); }
	
	public void changeNearSpawnPosition(SpawnPosition position) {
		if(position == nearSpawnStation) return;
		
		if(hunter != null) hunter.removeController(caputureController);
		nearSpawnStation = position;
				
		if(hunter != null && InitGame.get().isServer() && nearSpawnStation != null && nearSpawnStation instanceof SpawnStation) {
			SpawnStation station = (SpawnStation)nearSpawnStation;
			caputureController.setSpawnStation(station);
			
			hunter.addController(caputureController);
		}
		
		NearStationChangedEvent event = new NearStationChangedEvent(nearSpawnStation);
		playerStationListeners.callMethod(PlayerStationListener.NEARSPAWNSTATIONCHANGED, event);
	}
	
	public SpawnPosition getNearSpawnPosition() { return nearSpawnStation; }
	
	public void setUpBot(AILevel aiLevel) {
		assert(!isHuman());
		pilotController = new PilotController(this, aiLevel);
		team.getIngameState().getThreadPool().registerController(pilotController);
	}
	
	public void changeHunter(Hunter hunter) {
		changeHunter(hunter, false);
	}
	
	public void changeHunter(Hunter hunter, boolean isRegistered) {
		assert(hunter != null);
		Hunter oldHunter = this.hunter;
		removeHunter();
		this.hunter = hunter;
		hunter.setPilot(this);
		if(!isRegistered) InitGame.get().getNetworkState().registerSyncObject(hunter);
		
		if(isHuman()) hunter.removeAntiGravController();
		
		playerListeners.callMethod(PlayerListener.HUNTERCHANGED, new HunterChangedEvent(this, oldHunter, hunter));
	}
	
	public int getSpawnTime() { return spawnTime; }
	
	public void increaseDeaths() {
		isAlive = false;
		spawnTime = team.getIngameState().getGame().getRespawnTime();
		team.decreaseTickets();
		
		stats.increaseStat(PlayerStats.Deaths);
		
		playerValueManager.changeValue(ValueType.Deaths, getDeaths());
	}
	
	public boolean isSpawnable() {
		return !isAlive() && hunter != null && (currentSpawnPosition != null || !isHuman());
	}
	
	public void respawn() {
		assert(hunter != null);
		assert(!isAlive);
		
		spawnable = false;
		
		isAlive = true;
		hunter.restore();
		if(!isHuman()) setRandomSpawnPoint();
		assert(currentSpawnPosition != null);
		
		Vector3f randomSpawn = currentSpawnPosition.getSpawnLocation().getRandomSpawn();
		assert(randomSpawn != null);
		hunter.getLocalTranslation().set(randomSpawn);
		
		float rotationAngle = currentSpawnPosition.getSpawnLocation().getOrientationAngle();
		hunter.getLocalRotation().fromAngleNormalAxis(rotationAngle, Vector3f.UNIT_Y.clone());
		
		team.getIngameState().addNode(hunter);
		
		playerListeners.callMethod(PlayerListener.RESPAWNED, new RespawnEvent(this));
	}
	
	public void setRandomSpawnPoint() {
		assert(!isHuman());
		setSpawnLocation(team.getRandomSpawnPosition());
	}
	
	public void setSpawnLocation(SpawnPosition spawnPos) {
		assert(spawnPos != null);
		currentSpawnPosition = spawnPos;
		
		SelectedSpawnChangedEvent event = new SelectedSpawnChangedEvent(currentSpawnPosition);
		playerStationListeners.callMethod(PlayerStationListener.SELECTEDSPAWNPOSITIONCHANGED, event);
	}
	
	public SpawnPosition getCurrentSpawnPosition() { return currentSpawnPosition; }
	
	public Hunter getHunter() { return hunter; }
	
	public boolean isAlive() { return isAlive; }
	
	public Hunter getCurrentTarget() { return currentTarget; }
	
	public void changeBotTarget(TeamNode newBotTarget) {
		botTarget = newBotTarget;
		if(botTarget instanceof Hunter) setCurrentTarget((Hunter)botTarget);
	}
	
	public TeamNode getBotTarget() { return botTarget; }
	
	public void setCurrentTarget(Hunter newTarget) {
		if(newTarget != null && newTarget.getTeam() == team) return;
		Hunter oldTarget = currentTarget;
		currentTarget = newTarget;
		
		playerListeners.callMethod(PlayerListener.TARGETCHANGED, new TargetChangedEvent(this, currentTarget, oldTarget));
	}
	
	public void killed(Player victim, Weapon weapon) {
		playerListeners.callMethod(PlayerListener.KILLED, new KillEvent(this, victim, weapon));
	}
	
	public String getName() { return name; }

	public boolean isHuman() { return isHuman; }
	
	public Team getTeam() { return team; }
	
	public Team getEnemyTeam() { return enemyTeam; }
	
	public int getKills() { return stats.getIntValue(PlayerStats.Kills); }
	
	public int getDeaths() { return stats.getIntValue(PlayerStats.Deaths); }
	
	public void addKill() {
		team.increaseScore();
		stats.increaseStat(PlayerStats.Kills);
		
		playerValueManager.changeValue(ValueType.Kills, getKills());
	}
	
	public void setMoney(int money) {
		updateMoney(money);
	}
	
	public void addMoney(int amount) {
		assert(amount > 0);
		
		stats.increaseStat(PlayerStats.Received_Money, amount);
		
		updateMoney(getMoney() + amount);
	}
	
	public void spendMoney(int amount) {
		assert(amount <= getMoney());
		assert(amount >= 0);
		
		stats.increaseStat(PlayerStats.Spent_Money, amount);
		
		updateMoney(getMoney() - amount);
	}
	
	private void updateMoney(int newMoney) {
		if(newMoney == getMoney()) return;
		
		playerValueManager.changeValue(ValueType.Money, newMoney);
	}
	
	public int getMoney() { return playerValueManager.getIntValue(ValueType.Money); }
	
	public void removeHunter() {
		if(hunter != null) {
			hunter.removeFromParent();
			team.getIngameState().getHUDState().unRegisterTeamNode(hunter);
			hunter = null;
		}
	}
	
	public void switchTeam(Team newTeam) {
		if(newTeam == team) return;
		
		if(squad != null) leaveSquad();
		if(team != null) team.removePlayer(this);
		team = newTeam;
		team.addPlayer(this);
		enemyTeam = team.getEnemyTeam();
		
		removeHunter();
		
		setMoney(team.getPilotMoney());
		
		isAlive = false;
		
		playerTeamListeners.callMethod(PlayerTeamListener.TEAMCHANGED, team);
	}
	
	public void teamChangeFailed() {
		playerTeamListeners.callMethod(PlayerTeamListener.TEAMCHANGEFAILED);
	}
	
	public boolean isTeamMate(Player player) { return team.hasPlayer(player); }
	
	@Override
	public int getID() { return id; }
	
	@Override
	public void setID(int id) { this.id = id; }
	
	public boolean applyUpdateMessage(PlayerUpdateMessage pum) {
		switch(pum.getType()) {
			case Kills: addKill(); return true;
			case Deaths: increaseDeaths(); return true;
			case Respawn: if(isSpawnable()) respawn(); return true;
			case Money: setMoney(pum.getValue()); return true;
			case Ping: setPing(pum.getValue()); return true;
		}
		
		return false;
	}
	
	public PilotController getPilotController() { return pilotController; }
	
	public PlayerSpawnLocation getPlayerSpawnLocation() { return playerSpawnLocation; }
	
	public StatsContainer getStats() { return stats; }
	
	public int getPing() { return playerValueManager.getIntValue(ValueType.Ping); }
	
	public void setPing(int ping) {
		assert(ping >= 0);
		playerValueManager.changeValue(ValueType.Ping, ping);
	}
	
	@Override
	public int hashCode() { return id; }
	
	@Override
	public ListenerContainer getListenerContainer(Class<?> listenerClass) {
		if(listenerClass == PlayerListener.class) return playerListeners;
		if(listenerClass == PlayerStationListener.class) return playerStationListeners;
		if(listenerClass == PlayerMissionListener.class) return missionListeners;
		if(listenerClass == PlayerSquadListener.class) return squadListeners;
		if(listenerClass == PlayerTeamListener.class) return playerTeamListeners;
		if(listenerClass == PlayerAwardListener.class) return playerAwardListeners;
		if(listenerClass == PlayerValueListener.class) 
			return playerValueManager.getDefaultContainer().getListenerContainer();
		return null;
	}
	
	public void cleanUp () {
		changeNearSpawnPosition(null);
		
		team.getIngameState().removeGameTimeListener(gameTimeListener);
		team.removeMissionListener(missionListener);
		
		playerListeners.cleanUp();
		missionListeners.cleanUp();
		playerStationListeners.cleanUp();
		playerTeamListeners.cleanUp();
		playerAwardListeners.cleanUp();
		playerValueManager.getDefaultContainer().getListenerContainer().cleanUp();
	}
}